package instrumentation;

import java.util.HashMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.LinkedList;
import java.util.ArrayList;
import soot.ArrayType;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.HashSet;
import soot.Body;
import soot.BooleanType;
import soot.CharType;
import soot.DoubleType;
import soot.FloatType;
import soot.IntType;
import soot.IntegerType;
import soot.Local;
import soot.LongType;
import soot.SootMethod;
import soot.NullType;
import soot.PrimType;
import soot.RefLikeType;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.Type;
import soot.Unit;
import soot.jimple.ArrayRef;
import soot.jimple.AssignStmt;
import soot.jimple.CastExpr;
import soot.jimple.ReturnStmt;
import soot.jimple.ConditionExpr;
import soot.jimple.DefinitionStmt;
import soot.jimple.DoubleConstant;
import soot.jimple.EqExpr;
import soot.jimple.Expr;
import soot.jimple.FieldRef;
import soot.jimple.FloatConstant;
import soot.jimple.GeExpr;
import soot.jimple.GotoStmt;
import soot.jimple.GtExpr;
import soot.jimple.IdentityStmt;
import soot.jimple.IfStmt;
import soot.jimple.InstanceFieldRef;
import soot.jimple.IntConstant;
import soot.jimple.InvokeExpr;
import soot.jimple.Jimple;
import soot.jimple.LeExpr;
import soot.jimple.LongConstant;
import soot.jimple.LookupSwitchStmt;
import soot.jimple.LtExpr;
import soot.jimple.NeExpr;
import soot.jimple.NullConstant;
import soot.jimple.StaticFieldRef;
import soot.jimple.StaticInvokeExpr;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.VirtualInvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.StringConstant;
import soot.jimple.TableSwitchStmt;
import soot.jimple.internal.JArrayRef;
import soot.jimple.internal.JimpleLocal;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.graph.UnitGraph;
import soot.toolkits.scalar.CombinedAnalysis;
import soot.toolkits.scalar.CombinedDUAnalysis;
import soot.util.Chain;
import util.GAConfig;

import instrumentation.Instrumenter.UnifiedInstrumentator;
import instrumentation.ConditionIf.ConditionType;
public class WhiteInstrumenter implements UnifiedInstrumentator{
	private static final Logger logger = Logger.getLogger("WhiteInstrumenter");

	private static final SootClass trackerClass;
	private static SootMethod trackerSingleton;
	private static SootMethod getConditionTargetId;
	private static final SootMethod calculateConditionTargetDistance;
//
	private static final SootMethod branchHit;
	
	private static final SootMethod branchCovered;
	/** list of  uses for the fields  */
	private Set<DataUse> uses = new HashSet<DataUse>();


	static {

		final String TRACKER = TrackerCoverage.class.getName();
		Scene.v().loadClassAndSupport(TRACKER);
		trackerClass = Scene.v().getSootClass(TRACKER);
		trackerSingleton = trackerClass.getMethodByName("getTracker");
		
		getConditionTargetId = trackerClass.getMethodByName("getConditionTargetId");
		calculateConditionTargetDistance = trackerClass.getMethodByName("calculateConditionTargetDistance");
		branchHit = trackerClass.getMethodByName("branchHit");
		branchCovered = trackerClass.getMethodByName("branchCovered");

	}
	public WhiteInstrumenter() {
		System.out.println("instrumenter loaded");
	}
	private Analyzer analyzer;
	private Local localTracker;
	private Local localConditionTarget;
	private Local localTmpDouble1;
	private Local localTmpDouble2;
	private String methodName;
	private static int branchId=1;
	private static int switchCaseNoGenerator=1;

	private int falseBranch;
	private int trueBranch;
	Condition.DataType condDataType;
	private Type typeOp1;
	private Value constant=null;
	private boolean callMethod;
	private SootMethod method;
	private List<String> callee=new LinkedList<String>();
	@Override
	public int init(Body oldBody, Body newBody, Chain<Unit> newUnits, IdentityStmt[] paramDefs) {
		// some useful constants
		final SootMethod method = newBody.getMethod();
		final SootClass sClass = method.getDeclaringClass();
	
		final ConditionClass clazz = Factory.singleton.get(sClass, Factory.singleton.get(sClass.getFields()));

		localTracker = Jimple.v().newLocal("__tracker__", trackerClass.getType());
		newBody.getLocals().add(localTracker);
		newUnits.add(Jimple.v().newAssignStmt(localTracker, Jimple.v().newStaticInvokeExpr(trackerSingleton.makeRef())));

		localConditionTarget = Jimple.v().newLocal("condition_target__", IntType.v());
		newBody.getLocals().add(localConditionTarget);
	
//		newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef())));

		localTmpDouble1 = Jimple.v().newLocal("_tmp_double_1__", DoubleType.v());
		newBody.getLocals().add(localTmpDouble1);

		localTmpDouble2 = Jimple.v().newLocal("_tmp_double_2__", DoubleType.v());
		newBody.getLocals().add(localTmpDouble2);
		

		
		analyzer = new Analyzer(newUnits, clazz, newBody, oldBody, paramDefs);
		return branchId;

	}
	@Override
	public void done() {
		for(ConditionClass c : Factory.singleton.getClasses()) {
			//	c.performDataFlowAnalysis();
				c.write(GAConfig.getDirInstrumented(), c.getCName());
		}
	}
	@Override
	public void processPre(Chain<Unit> newUnits, Stmt op) {
		analyzer.process(newUnits, op);
	}
	@Override
	public int processPost(Chain<Unit> newUnits, Stmt op) {
		analyzer.processPost(newUnits, op);
		return branchId;
	}
	class Analyzer {
		private final CombinedAnalysis duAnalysis;
		private final Map<Local, Data> localRepository;
		private final Map<Unit, Set<Integer>> toLinkMap;
		private  ConditionClass clazz;
		final boolean methodPrivate;
		/** link the first unit of each building block to the set of incoming edges (to complete) */
		
		public Analyzer(Chain<Unit> newUnits,ConditionClass clazz1, Body newBody, Body oldBody, IdentityStmt[] paramDefs) {
			 clazz=clazz1;
			 method = newBody.getMethod();
			 methodName = method.getName();
			 methodPrivate = method.isPrivate();
			final UnitGraph unitGraph = new ExceptionalUnitGraph(oldBody);
			duAnalysis = CombinedDUAnalysis.v(unitGraph);
			toLinkMap = new HashMap<Unit, Set<Integer>>();
			localRepository = new HashMap<Local, Data>();
			for(int i = 0; i < paramDefs.length; i++) {
				final Local localParam = newBody.getParameterLocal(i);
				Data dataParam = new Data(null,localParam.getType().toString(),i, true);
				localRepository.put(localParam, dataParam);
			}
			
		}
		private void preProcess(Chain<Unit> newUnits, Stmt stmt) {
			
			if(stmt instanceof IfStmt){
				falseBranch=branchId++;
				trueBranch=branchId++;

				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchHit.makeRef(), IntConstant.v(falseBranch))));
				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchHit.makeRef(), IntConstant.v(trueBranch))));
			}
			// take care of uses
			if(stmt instanceof ReturnStmt ){//|| (stmt instanceof AssignStmt)) {
					for(soot.ValueBox useBox : stmt.getUseBoxes()) {
						handleUse(newUnits, useBox.getValue(), stmt);
				}
			}

		}
		private String handleUse(Chain<Unit> newUnits, soot.Value v, Stmt u) {
			if(v == null) return null;
			v = getRealUse(v, u, false);
			callee.clear();
			constant=null;
	        callMethod=false;

			if(v == null) return null;

//			if(v instanceof InstanceFieldRef) {
//			//	Data data = getData(v);
//			//	if(data == null) return null;
//				InstanceFieldRef fr = (InstanceFieldRef) v;
//				SootField field = fr.getField();
			DataUse use=null;
			if(v instanceof InstanceFieldRef){
			if(method.getDeclaringClass().getInterfaces()!=null)
				for( SootClass clazz:method.getDeclaringClass().getInterfaces()){
					if(clazz.getMethods().size()!=0 ){
						List<SootMethod>  list=clazz.getMethods();
						for(SootMethod sInterfaceMethod:list){
//							System.out.println(sInterfaceMethod.getSubSignature());
//							System.out.println(method.getSubSignature());
							if(sInterfaceMethod.getSubSignature().equals(method.getSubSignature())){
							    use = new DataUse(method.getDeclaringClass().toString(),sInterfaceMethod.toString(), v);
								uses.add(use);
								break;
							}
					}
					}
				}
			if(use==null){
					 use = new DataUse(method.getDeclaringClass().toString(), method.toString(), v);
					uses.add(use);				
				}
				}
//			else{
//				DataUse use = new DataUse(null, method.toString(), v);
//				uses.add(use);		
//			}
			return null;
		}
		private soot.Value getUseField(String method) {
			int noReturn=0;
			soot.Value value=null;
			for(DataUse useField: uses){
				if(useField.getMName().equals(method)){
					value=useField.getData();
					noReturn++;
				}
			}
			if(noReturn==1&&value!=null) return value;
				return null;
		}


		public void process(Chain<Unit> newUnits, Stmt op) {
			preProcess(newUnits, op);
	//		System.out.println(op);
			if(op.containsInvokeExpr())
				processInvokeExpr(newUnits,(Stmt)  op);	
			if(op instanceof AssignStmt)
				process(newUnits, (AssignStmt) op);
			else if(op instanceof IdentityStmt)
				process(newUnits, (IdentityStmt) op);
			else if(op instanceof IfStmt)
				process(newUnits, (IfStmt) op);
			else if(op instanceof LookupSwitchStmt)
				process(newUnits, (LookupSwitchStmt) op);
			else if(op instanceof TableSwitchStmt)
				process(newUnits, (TableSwitchStmt) op);
			else if(op instanceof ReturnStmt)
				process(newUnits, (ReturnStmt) op);
			else {
				logger.info("WhiteInstrumenter.process cannot analyze " + op + " (" + op.getClass().getName() + ")");
			}
		}
		public void processInvokeExpr(Chain<Unit> newUnits,Stmt op) {
		InvokeExpr iexpr = (InvokeExpr)op.getInvokeExpr();
		    if (iexpr instanceof SpecialInvokeExpr)
		    {
		        SootMethod target = ((SpecialInvokeExpr)iexpr).getMethod();
		        if(!target.getName().equals("<init>"))
		        if(!Instrumenter.calls.contains(target.getName()))
		        		Instrumenter.calls.add(new String(target.getName()));
		    }
		    if (iexpr instanceof StaticInvokeExpr)
		    {
		        SootMethod target = ((StaticInvokeExpr)iexpr).getMethod();
		        if(!target.getName().equals("<init>"))
		        if(!Instrumenter.calls.contains(target.getName()))
		        		Instrumenter.calls.add(new String(target.getName()));
		    }

		}
		public void processFieldRef(Chain<Unit> newUnits, Stmt op){
			System.out.print(op.getFieldRef().getField());
			
		}
		public void process(Chain<Unit> newUnits, IfStmt u) {
			ConditionExpr expr = (ConditionExpr) u.getCondition();

			soot.Value op1 = expr.getOp1();
			soot.Value op2 = expr.getOp2();
			typeOp1 = op1.getType();

         System.out.println("trueBranch="+ trueBranch);
         Stmt useStmt = u;
          condDataType = getType(op1);
         Value v2=null;
         callMethod=false;
		 Value v1 = getValue(getRealUse(op1, useStmt, false));
		
		 if(constant==null)
			 v2 = getValue(getRealUse(op2, useStmt, false));
		 else
			 v2=constant;
			ConditionIf.ConditionType condType = null;
			if(expr instanceof LtExpr) condType = ConditionType.LT;
			if(expr instanceof LeExpr) condType = ConditionType.LE;
			if(expr instanceof NeExpr) condType = ConditionType.NE;
			if(expr instanceof EqExpr) condType = ConditionType.EQ;
			if(expr instanceof GeExpr) condType = ConditionType.GE;
			if(expr instanceof GtExpr) condType = ConditionType.GT;
			System.out.println(callee.toString());
			for(int i=callee.size();i<2;i++){
					callee.add("");
			}
				
			ConditionIf tCondition = new ConditionIf(clazz,methodName,methodPrivate,callMethod,trueBranch, expr.toString(),  condDataType, callee.get(0),v1, condType,callee.get(1), v2);

			ConditionIf fCondition = new ConditionIf(clazz,methodName,methodPrivate,callMethod,falseBranch, expr.toString(), condDataType, callee.get(0),v1, condType,callee.get(1), v2);
			callee.clear();
			constant=null;
	        callMethod=false;
			// the expression "if(expr)" is instrumented this way:
			//  1. if(expr) goto 10
			//  2. branchCovered(false)
			//  2a trackPUse(false, usesOfExpr)  { if PUse tracking}
			//  3. if(condTarget != trueBranch) goto 7
			//  4. trackDistance(op1, op2);
			//  5. goto 17
			//  7. if(contTarget != falseBranch) goto 17
			//  8. trackDistance(0);
			//  9. goto 17

			// 10. branchCovered(true)
			// 10a trackPUse(true, usesOfExpr)  { if PUse tracking}
			// 11. if(condTarget != trueBranch) goto 14
			// 12. trackDistance(0);
			// 13. goto 17
			// 14. if(contTarget != falseBranch) goto 17
			// 15. trackDistance(op1, op2);
			// 16. goto 17
			// 17. nop (after)

			Unit after = Jimple.v().newNopStmt();

			Unit handleTrue = Jimple.v().newNopStmt();
			
			newUnits.add(Jimple.v().newIfStmt(expr, handleTrue));
			{ // handle false

				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchCovered.makeRef(), IntConstant.v(falseBranch))));
				newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),IntConstant.v(trueBranch))));

				
				// calculate distance (true)
				Unit nop = Jimple.v().newNopStmt();
				newUnits.add(Jimple.v().newIfStmt(Jimple.v().newNeExpr(localConditionTarget, IntConstant.v(trueBranch)), nop));
				if(typeOp1 instanceof BooleanType){
				}//if(type instanceof BooleanType){
				else if(typeOp1 instanceof IntegerType || typeOp1 instanceof LongType || typeOp1 instanceof FloatType || typeOp1 instanceof DoubleType) {
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble1,
							(op1.getType() instanceof DoubleType) ? op1 : Jimple.v().newCastExpr(op1, DoubleType.v())));

					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble2,
							(op2.getType() instanceof DoubleType) ? op2 : Jimple.v().newCastExpr(op2, DoubleType.v())));

					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), Arrays.asList(localTmpDouble1, localTmpDouble2))));
				} else if(typeOp1 instanceof RefLikeType) {

					// localTmpDouble1 = (op1 == null) ? 0 : 1;
					Unit isNull = Jimple.v().newNopStmt();
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble1, DoubleConstant.v(0)));
					newUnits.add(Jimple.v().newIfStmt(Jimple.v().newEqExpr(op1, NullConstant.v()), isNull));
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble1, DoubleConstant.v(1)));
					newUnits.add(isNull);

					// localTmpDouble2 = (op2 == null) ? 0 : 1;
					isNull = Jimple.v().newNopStmt();
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble2, DoubleConstant.v(0)));
					newUnits.add(Jimple.v().newIfStmt(Jimple.v().newEqExpr(op2, NullConstant.v()), isNull));
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble2, DoubleConstant.v(1)));
					newUnits.add(isNull);
					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), Arrays.asList(localTmpDouble1, localTmpDouble2))));

				}
				newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),IntConstant.v(falseBranch))));
				newUnits.add(Jimple.v().newGotoStmt(after));
				// calculate distance (false)
				newUnits.add(nop);
				newUnits.add(Jimple.v().newIfStmt(Jimple.v().newNeExpr(localConditionTarget, IntConstant.v(falseBranch)), after));
				newUnits.add(Jimple.v().newGotoStmt(after));
			}
			newUnits.add(handleTrue);
			{ // handle true
				// 10. branchCovered(true)
				// 10a trackPUse(true, usesOfExpr)  { if PUse tracking}
				// 11. if(condTarget != trueBranch) goto 14
				// 12. trackDistance(0);
				// 13. goto 17
				// 14. if(contTarget != falseBranch) goto 17
				// 15. trackDistance(op1, op2);
				// 16. goto 17
				// 17. nop (after)

				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchCovered.makeRef(), IntConstant.v(trueBranch))));
				newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),IntConstant.v(falseBranch))));
				// calculate distance (true)
				Unit nop = Jimple.v().newNopStmt();
				newUnits.add(Jimple.v().newIfStmt(Jimple.v().newNeExpr(localConditionTarget, IntConstant.v(trueBranch)), nop));
		
				newUnits.add(Jimple.v().newGotoStmt(after));

				// calculate distance (false)
				newUnits.add(nop);
				newUnits.add(Jimple.v().newIfStmt(Jimple.v().newNeExpr(localConditionTarget, IntConstant.v(falseBranch)), after));

				if(typeOp1 instanceof BooleanType) {
				}
				else if(typeOp1 instanceof IntegerType || typeOp1 instanceof LongType || typeOp1 instanceof FloatType || typeOp1 instanceof DoubleType) {
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble1,
							(op1.getType() instanceof DoubleType) ? op1 : Jimple.v().newCastExpr(op1, DoubleType.v())));

					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble2,
							(op2.getType() instanceof DoubleType) ? op2 : Jimple.v().newCastExpr(op2, DoubleType.v())));

					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), Arrays.asList(localTmpDouble1, localTmpDouble2))));

				} else if(typeOp1 instanceof RefLikeType) {

					// localTmpDouble1 = (op1 == null) ? 0 : 1;
					Unit isNull = Jimple.v().newNopStmt();
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble1, DoubleConstant.v(0)));
					newUnits.add(Jimple.v().newIfStmt(Jimple.v().newEqExpr(op1, NullConstant.v()), isNull));
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble1, DoubleConstant.v(1)));
					newUnits.add(isNull);

					// localTmpDouble2 = (op2 == null) ? 0 : 1;
					isNull = Jimple.v().newNopStmt();
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble2, DoubleConstant.v(0)));
					newUnits.add(Jimple.v().newIfStmt(Jimple.v().newEqExpr(op2, NullConstant.v()), isNull));
					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble2, DoubleConstant.v(1)));
					newUnits.add(isNull);
					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), Arrays.asList(localTmpDouble1, localTmpDouble2))));

				}
				newUnits.add(Jimple.v().newGotoStmt(after));
			}

			// done
			newUnits.add(after);
	//		current = null;
		}
		public  Condition.DataType getType(soot.Value v) {

			Type type = v.getType();

			if(type instanceof BooleanType)
				return Condition.DataType.Boolean;

			if(type instanceof CharType)
				return Condition.DataType.Character;

			if(type instanceof PrimType)
				return Condition.DataType.Number;

			if(type instanceof RefType) {
				if(((RefType) type).getClassName().equals("java.lang.String"))
					return Condition.DataType.String;

				return Condition.DataType.Reference;
			}

			if(type instanceof NullType)
				return Condition.DataType.Reference;

			if(type instanceof ArrayType)
				return Condition.DataType.Array;

		//	if(TestFul.DEBUG) TestFul.debug("Unknown data type: " + type);


			return Condition.DataType.Reference;
		}
		public void process(Chain<Unit> newUnits, LookupSwitchStmt u) {
			final soot.Value key = u.getKey();
			Value v1 = getValue(getRealUse(key, u, false));
			int current= switchCaseNoGenerator++;
			ConditionSwitch c = new ConditionSwitch(clazz,methodName,methodPrivate,current,v1);

			List<IntConstant> lookupValues = new ArrayList<IntConstant>();
			List<Unit> targets = new ArrayList<Unit>();

			int[] keys = new int[u.getTargetCount()];
			SortedMap<Integer, Unit> keyTarget = new TreeMap<Integer, Unit>();
			Map<Integer, Integer> keyBranchId = new HashMap<Integer, Integer>();
			for(int i = 0; i < u.getTargetCount(); i++) {
				final int value = u.getLookupValue(i);
				keys[i] = value;
				lookupValues.add(IntConstant.v(value));

		
				int caseNo=addBranch( u.getTarget(i));
			    keyBranchId.put(value, caseNo);
				c.addCase(caseNo,value);

				Unit target = Jimple.v().newNopStmt();
				targets.add(target);
				keyTarget.put(value, target);
				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchHit.makeRef(), IntConstant.v(caseNo))));

			}

			Unit defaultTarget = Jimple.v().newNopStmt();
			Integer defaultBranchId;
			{
				defaultBranchId=addBranch( u.getDefaultTarget());
				c.setDefaultCase(defaultBranchId);
				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchHit.makeRef(), IntConstant.v(defaultBranchId))));


			}

			Unit lastNop = Jimple.v().newNopStmt();

			newUnits.add(Jimple.v().newLookupSwitchStmt(key, lookupValues, targets, defaultTarget));
			processSwitch(newUnits, u, key, keys, keyTarget, keyBranchId, defaultTarget, defaultBranchId, lastNop);
			newUnits.add(lastNop);


		}
		public void process(Chain<Unit> newUnits, TableSwitchStmt u) {
			final soot.Value key = u.getKey();
			Value v1 = getValue(getRealUse(key, u, false));
			
			final int lIndex = u.getLowIndex();
			final int hIndex = u.getHighIndex();
			int current= switchCaseNoGenerator++;
			ConditionSwitch c = new ConditionSwitch(clazz,methodName,methodPrivate,current,v1);

			int[] keys = new int[hIndex-lIndex+1];
			List<Unit> targets = new ArrayList<Unit>();
			SortedMap<Integer, Unit> keyTarget = new TreeMap<Integer, Unit>();
			Map<Integer, Integer> keyBranchId = new HashMap<Integer, Integer>();
			for(int idx = 0; idx <= hIndex-lIndex; idx++) {
				final int value = idx+lIndex;
				keys[idx] = value;

			//	EdgeConditional edge = new EdgeConditional(current, c);
			//	c.addCase(value, edge);
			//	add(edge, u.getTarget(idx));
			//	keyBranchId.put(value, edge.getId());

				
				int caseNo=addBranch( u.getTarget(idx));
			    keyBranchId.put(value, caseNo);
				c.addCase(caseNo,value);

				Unit target = Jimple.v().newNopStmt();
				targets.add(target);
				keyTarget.put(value, target);
				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchHit.makeRef(), IntConstant.v(caseNo))));
			}
			Unit defaultTarget = Jimple.v().newNopStmt();
			Integer defaultBranchId;
			{
				defaultBranchId=addBranch( u.getDefaultTarget());
				c.setDefaultCase(defaultBranchId);
				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchHit.makeRef(), IntConstant.v(defaultBranchId))));

			}
			Unit lastNop = Jimple.v().newNopStmt();

			newUnits.add(Jimple.v().newTableSwitchStmt(key, u.getLowIndex(), u.getHighIndex(), targets, defaultTarget));
			processSwitch(newUnits, u, key, keys, keyTarget, keyBranchId, defaultTarget, defaultBranchId, lastNop);
			newUnits.add(lastNop);
			
		}
		private void processSwitch(Chain<Unit> newUnits, Unit switchUnit, soot.Value keyValue, int[] keys, SortedMap<Integer, Unit> keyTarget, Map<Integer, Integer> keyBranchId, Unit defaultTarget, int defaultBranchId, Unit lastNop) {
			for(final int key : keys) {
				final Unit start = keyTarget.get(key);
				final int branchId = keyBranchId.get(key);

				newUnits.add(start);

				// track distance

				// [0..i..n-1] i-th target; [n] -> default target;
				IntConstant[] ctlookupValues = new IntConstant[keys.length + 1];
				Unit[] ctTargets = new Unit[keys.length + 1];
				for(int i = 0; i < keys.length; i++) {
					ctlookupValues[i] = IntConstant.v(keyBranchId.get(keys[i]));
					ctTargets[i] = Jimple.v().newNopStmt();
				}
				ctlookupValues[keys.length] =  IntConstant.v(defaultBranchId);
				ctTargets[keys.length] = Jimple.v().newNopStmt();
				
				newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),ctlookupValues[0])));
				newUnits.add(Jimple.v().newLookupSwitchStmt(localConditionTarget, Arrays.asList(ctlookupValues), Arrays.asList(ctTargets), lastNop));

		
				for(int i = 0; i < keys.length; i++) {
					final int ctKey = keys[i];
					newUnits.add(ctTargets[i]);
					newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),IntConstant.v(keyBranchId.get(ctKey)))));

					if(ctKey == key) // branch executed!
//						newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, setConditionTargetDistance.makeRef(), DoubleConstant.v(-1.0), (keyDef == null ? NullConstant.v() : keyDef))));
								newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchCovered.makeRef(), IntConstant.v(branchId))));
					else
						newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), Arrays.asList(DoubleConstant.v(ctKey),DoubleConstant.v(key) ))));

//						newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, setConditionTargetDistance.makeRef(), DoubleConstant.v(Math.abs(ctKey - key)), (keyDef == null ? NullConstant.v() : keyDef))));
//					newUnits.add(Jimple.v().newGotoStmt(lastNop));
				}

				{ // handle default target
					newUnits.add(ctTargets[keys.length]);
//					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), DoubleConstant.v(switchDistanceToDefault(key, keyBranchId.keySet())))));
					newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),IntConstant.v(defaultBranchId))));

					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), Arrays.asList(DoubleConstant.v(switchDistanceToDefault(key, keyBranchId.keySet())),DoubleConstant.v(0) ))));
					//
					newUnits.add(Jimple.v().newGotoStmt(lastNop));
				}
			}

			// manage default target
			{
				newUnits.add(defaultTarget);
				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchCovered.makeRef(), IntConstant.v(defaultBranchId))));
//				newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, trackBranch.makeRef(), IntConstant.v(defaultBranchId))));


				// track distance
				IntConstant[] ctlookupValues = new IntConstant[keys.length + 1];
				Unit[] ctTargets = new Unit[keys.length + 1];
				for(int i = 0; i < keys.length; i++) {
					ctlookupValues[i] = IntConstant.v(keyBranchId.get(keys[i]));
					ctTargets[i] = Jimple.v().newNopStmt();
				}
				ctlookupValues[keys.length] =  IntConstant.v(defaultBranchId);
				ctTargets[keys.length] = Jimple.v().newNopStmt();
				newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),ctlookupValues[0])));
				newUnits.add(Jimple.v().newLookupSwitchStmt(localConditionTarget, Arrays.asList(ctlookupValues), Arrays.asList(ctTargets), lastNop));

				for(int i = 0; i < keys.length; i++) {
					final int ctKey = keys[i];
					newUnits.add(ctTargets[i]);
					
					newUnits.add(Jimple.v().newAssignStmt(localConditionTarget, Jimple.v().newVirtualInvokeExpr(localTracker, getConditionTargetId.makeRef(),IntConstant.v(keyBranchId.get(keys[i])))));

					newUnits.add(Jimple.v().newAssignStmt(localTmpDouble1, Jimple.v().newCastExpr(keyValue, DoubleType.v())));
					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, calculateConditionTargetDistance.makeRef(), Arrays.asList(DoubleConstant.v(ctKey), localTmpDouble1))));
//					newUnits.add(Jimple.v().newGotoStmt(lastNop));
				}

				{ // handle default target
					newUnits.add(ctTargets[keys.length]);
					newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(localTracker, branchCovered.makeRef(), IntConstant.v(defaultBranchId))));
					newUnits.add(Jimple.v().newGotoStmt(lastNop));
				}
			}

		}
		private int  addBranch( Unit stmt) {
			int caseNo=branchId++;
			Set<Integer> set = toLinkMap.get(stmt);
			if(set == null) {
				set = new HashSet<Integer>();
				toLinkMap.put(stmt, set);		
			}
			set.add(caseNo);
				
			return caseNo;
		}
		private int switchDistanceToDefault(int key, Set<Integer> keySet) {
			if(!keySet.contains(key)) return 0;

			int d = 1;
			while(keySet.contains(key-d) && keySet.contains(key + d)) d++;

			return d;
		}


		private Data getData(soot.Value value) {

			if(value == null) return null;
			if(value instanceof Constant) return null;
			if(value instanceof Expr) return null;

			if(value instanceof Local) {
				Local local = (Local) value;
				Data ret = localRepository.get(local);
				if(ret == null) {
						ret = new Data(null,null,-1, false);
					localRepository.put(local, ret);
				}
				return ret;
			}

			if(value instanceof FieldRef) return Factory.singleton.get(((FieldRef) value).getField());
			return null;
		}

		public Value getValue(soot.Value v) {
			if (v instanceof soot.jimple.Constant) {

				if(v instanceof NullConstant) return new ConstantString(null);
				if(v instanceof IntConstant) return new Constant(((IntConstant) v).value);
				if(v instanceof LongConstant) return new Constant(((LongConstant) v).value);
				if(v instanceof FloatConstant) return new Constant(((FloatConstant) v).value);
				if(v instanceof DoubleConstant) return new Constant(((DoubleConstant) v).value);
				if(v instanceof StringConstant) return new ConstantString(((StringConstant) v).value);;

				return null;
			}

			if(v instanceof Local || v instanceof FieldRef)
				return getData(v);

			if(v instanceof Expr) return null;

			if(v instanceof ArrayRef) return null;

			return null;
		}		
		public void processPost(Chain<Unit> newUnits, Stmt stmt) {

		}		
		private soot.Value getRealUse(soot.Value v, Unit u, boolean skipTrivial) {
	
			if(v == null) return null;


			if(u == null) return v;

			if(v instanceof Local) {

				List<Unit> reachingDefs = duAnalysis.getDefsOfAt((Local) v, u);

				if(reachingDefs.size() < 1) {
					if(skipTrivial) return null;
					return v;
				}

				// if the use has only one reaching definition, I can skip its tracking or check if it is a temporary variable
				if(reachingDefs.size() == 1) {

					// check if it is a temporary variable used to access a field
					Unit def = reachingDefs.get(0);

					if(def instanceof AssignStmt) {
						AssignStmt a = (AssignStmt)def;

						soot.Value rightOp = a.getRightOp();
						
						if((rightOp instanceof FieldRef) && duAnalysis.getUsesOf(def).size() == 1) {
								return getRealUse(rightOp, def, skipTrivial);

						} else if(rightOp instanceof CastExpr) {
							soot.Value op = ((CastExpr) rightOp).getOp();
							return getRealUse(op, def, skipTrivial);

						}else if(rightOp instanceof SpecialInvokeExpr){// method calls ,e.g. if( isFull())
							SpecialInvokeExpr fr = (SpecialInvokeExpr) rightOp;
							callMethod=true;
//							System.out.println(" SpecialInvokeExpr: ToDo");
//							System.exit(1);
						}else if(rightOp instanceof VirtualInvokeExpr ){// it looks like all the String function calls are  VirtualInvokeExpr
							VirtualInvokeExpr fr = (VirtualInvokeExpr) rightOp;
							v= getUseField( fr.getMethodRef().toString());
							if(v!=null) return v;
							soot.Value op1 = fr.getBase();
							v=  getRealUse(op1, def, skipTrivial);							
							condDataType=getType(v);
							if(condDataType.equals(condDataType.Reference)){
								String  methodCall= fr.getMethodRef().name();
								//v=  getRealUse(methodCall, def, skipTrivial);
								System.out.print("Call a method: v1= "+ v.getType().toString() + " methodCall: "+ methodCall);
								//System.exit(1);
							}
							if(!fr.getMethod().getName().equals("charAt"))
							if(!fr.getMethod().getName().equals("length")) 
							if(fr.getArgCount()>0){
								skipTrivial=true;
								soot.Value arg=fr.getArg(0);
								getRealUse(arg, def, skipTrivial);
								}
							
							System.out.print("VirtualInvokeExpr: v1= "+ v.toString() + " condDataType: "+ condDataType.toString()+" v2: ");
							System.out.println( constant==null?"null":constant);
							}else
								if(rightOp instanceof InstanceFieldRef) {
									InstanceFieldRef fr = (InstanceFieldRef) rightOp;
									v= getRealUse(rightOp, def, skipTrivial);
									//System.out.println(" rightOp instanceof InstanceFieldRef: ToDo");
									//System.exit(1);
									}else
										if(rightOp instanceof soot.jimple.internal.JCmpExpr ||rightOp instanceof soot.jimple.internal.JCmpgExpr){
											soot.Value op1,op2;
											if(rightOp instanceof soot.jimple.internal.JCmpExpr){
												soot.jimple.internal.JCmpExpr fr=(soot.jimple.internal.JCmpExpr) rightOp;
												op1 = fr.getOp1();
												op2 = fr.getOp2();
											}else{
												soot.jimple.internal.JCmpgExpr fr=(soot.jimple.internal.JCmpgExpr) rightOp;
												op1 = fr.getOp1();
												op2 = fr.getOp2();
											}
											v= getRealUse(op1, def, skipTrivial);
											soot.Value v2 =  getRealUse(op2, def, skipTrivial);
										}else 
											if(rightOp instanceof soot.jimple.internal.JStaticInvokeExpr){//r0.<shoppingcart.products.Product: double price>
												soot.jimple.internal.JStaticInvokeExpr fr = (soot.jimple.internal.JStaticInvokeExpr) rightOp;
												skipTrivial=true;
												if(fr.getArgCount()>0)
													v=getRealUse(fr.getArg(0), def, skipTrivial);
//												System.out.println(" soot.jimple.internal.JStaticInvokeExpr: ToDo");
//												System.exit(1);

											}else
												if( rightOp instanceof soot.jimple.internal.JInterfaceInvokeExpr){
													soot.jimple.internal.JInterfaceInvokeExpr fr = (soot.jimple.internal.JInterfaceInvokeExpr) rightOp;
													v= getUseField( fr.getMethodRef().toString());
													if(v!=null) return v;

													soot.Value op = fr.getBase();

													v=getRealUse(op, def, skipTrivial);
													if(fr.getArgCount()!=0){
														soot.Value v1=fr.getArg(0);
														skipTrivial=true;
														constant= getValue(getRealUse(v1, def, skipTrivial));
														System.out.println(" soot.jimple.internal.JInterfaceInvokeExpr: ToDo");
													}
												}else
													if( rightOp instanceof soot.jimple.internal.JAndExpr||rightOp instanceof soot.jimple.internal.JOrExpr){
														soot.jimple.internal.JAndExpr fr = (soot.jimple.internal.JAndExpr) rightOp;
														soot.Value op1 = fr.getOp1();
														soot.Value v1 =  getRealUse(op1, def, skipTrivial);
														return v1;
														//soot.Value op2 = fr.getOp2();
														//soot.Value v2 =  getRealUse(op2, def, skipTrivial);
														//System.out.println(" soot.jimple.internal.JAndExpr: ToDo");
														//System.exit(1);
													}else
														if( rightOp instanceof soot.jimple.internal.JSubExpr||rightOp instanceof soot.jimple.internal.JAddExpr
														|| rightOp instanceof soot.jimple.internal.JMulExpr||rightOp instanceof soot.jimple.internal.JAddExpr){
															soot.Value op1,op2;
															if(rightOp instanceof soot.jimple.internal.JSubExpr){
																soot.jimple.internal.JSubExpr fr = (soot.jimple.internal.JSubExpr) rightOp;
																 op1 = fr.getOp1();
																 op2 = fr.getOp2();
															}else
															if(rightOp instanceof soot.jimple.internal.JAddExpr){
																soot.jimple.internal.JAddExpr fr= (soot.jimple.internal.JAddExpr) rightOp;
																System.out.println(fr);
																op1 = fr.getOp1();
																 op2 = fr.getOp2();
															}else{
															//if(rightOp instanceof soot.jimple.internal.JMulExpr){
																soot.jimple.internal.JMulExpr fr= (soot.jimple.internal.JMulExpr) rightOp;
																System.out.println(fr);
																op1 = fr.getOp1();
																 op2 = fr.getOp2();
															}

															 v=getRealUse(op1, def, skipTrivial);
															
															// getRealUse(op2, def, skipTrivial);
														}else if(rightOp instanceof soot.jimple.internal.JInstanceOfExpr){
															soot.jimple.internal.JInstanceOfExpr fr= (soot.jimple.internal.JInstanceOfExpr) rightOp;
															soot.Value op1 = fr.getOp();
															
															v= getRealUse(op1, def, skipTrivial);
																System.out.println(" soot.jimple.internal.JInstanceOfExpr: TODO");
																//System.exit(1);

															
														}
														else if(rightOp instanceof JArrayRef) {
															soot.jimple.internal.JArrayRef fr = (soot.jimple.internal.JArrayRef) rightOp;
															soot.Value op1 = fr.getBase();
															 getRealUse(op1, def, skipTrivial);

															//System.out.println(" instance of: " + rightOp);
															}if(rightOp instanceof soot.jimple.internal.JimpleLocal) {
																//System.out.println(" soot.jimple.internal.JimpleLocal: ToDo");
																//System.exit(1);
																return rightOp;
																//getRealUse(rightOp, def, skipTrivial);
															}
//															else{
//																System.out.println("\t-------------------------------");
//																System.out.println("\tWe need to analysis: "+rightOp.toString() );
//																System.out.println("\t-------------------------------");
//															}

					}else if(def instanceof IdentityStmt) {
						IdentityStmt stmt = (IdentityStmt)def;
						final soot.Value leftOp = stmt.getLeftOp();
						if(skipTrivial)
							constant=getValue(leftOp);
						v=	leftOp;
					}else if(def instanceof soot.jimple.internal.JAssignStmt) {
						soot.jimple.internal.JAssignStmt stmt = (soot.jimple.internal.JAssignStmt)def;
						System.out.println(" def instanceof soot.jimple.internal.JAssignStmt: ToDo");
						System.exit(1);
					}


					return v;
				}
			}

			if(v instanceof InstanceFieldRef) {
				InstanceFieldRef fr = (InstanceFieldRef) v;
				if(skipTrivial)
					constant=getValue(fr);
				soot.Value calee = fr.getBase();
				getRealUseObject(calee, u, skipTrivial);
//				SootClass c= fr.getFieldRef().declaringClass();
//				int indx=callee.size();
//				callee.add(indx, c.getName());

				System.out.println(" constant="+constant);
			}else if(v instanceof StaticFieldRef) {
				StaticFieldRef fr = (StaticFieldRef) v;
				SootClass c= fr.getFieldRef().declaringClass();
				int indx=callee.size();
				callee.add(indx, c.getName());
				if(skipTrivial)
					constant=getValue(fr);
			}else if(v instanceof NullConstant) {
				NullConstant fr = (NullConstant) v;
				constant=getValue(fr);	
			}else if(v instanceof IntConstant) {
				IntConstant fr = (IntConstant) v;
				constant=getValue(fr);
				System.out.println(" constant="+fr.value);
			}else if(v instanceof JimpleLocal) {
				JimpleLocal fr = (JimpleLocal) v;
				List<Unit> reachingDefs = duAnalysis.getDefsOfAt((Local) v, u);
				for(int i=0;i<reachingDefs.size();i++){
				Unit def = reachingDefs.get(i);
				if(def instanceof AssignStmt) {
					AssignStmt a = (AssignStmt)def;
					soot.Value rightOp = a.getRightOp();				
					System.out.print("\n\t **Skipping instrumentation of the definition of parameter="+fr.toString());
				}else if(def instanceof IdentityStmt) {
					IdentityStmt stmt = (IdentityStmt)def;
					soot.Value rightOp = stmt.getLeftOp();
//					System.out.println(" def instanceof IdentityStmt: ToDo");
					return rightOp;
				//	System.exit(1);

				}
				}
			}else if(v instanceof soot.jimple.StringConstant){
				StringConstant fr = (StringConstant) v;
				constant=getValue(fr);
				System.out.println("StringConstant:  constant= "+ fr.value);
			}else if(v instanceof soot.jimple.DoubleConstant){
				DoubleConstant fr = (DoubleConstant) v;
				constant=getValue(fr);
				System.out.println("DoubleConstant:  constant= "+ fr.value);
			}

				
			return v;
		}
		public void process(Chain<Unit> newUnits, ReturnStmt u) {
			
		}

		public void process(Chain<Unit> newUnits, DefinitionStmt stmt) {
			
		}
		private String	   getRealUseObject(soot.Value v, Unit u, boolean skipTrivial) {
			String calleeObj=null;
			if(v == null) return calleeObj;
			if(u == null) return calleeObj;
			if(v instanceof Local) {
				List<Unit> reachingDefs = duAnalysis.getDefsOfAt((Local) v, u);
				if(reachingDefs.size() < 1) {
					if(skipTrivial) return calleeObj;
					return calleeObj;
				}
				// if the use has only one reaching definition, I can skip its tracking or check if it is a temporary variable
				if(reachingDefs.size() == 1) {
					// check if it is a temporary variable used to access a field
					Unit def = reachingDefs.get(0);
					if(def instanceof AssignStmt) {
						AssignStmt a = (AssignStmt)def;
						soot.Value rightOp = a.getRightOp();					
						if((rightOp instanceof FieldRef) ){//&& duAnalysis.getUsesOf(def).size() == 1) {
							 getRealUseObject(rightOp, def, skipTrivial);
					}else if(rightOp instanceof CastExpr) {
						soot.Value op = ((CastExpr) rightOp).getOp();
						 getRealUseObject(op, def, skipTrivial);
					}
				}else if(def instanceof IdentityStmt) {
					IdentityStmt stmt = (IdentityStmt)def;
					soot.Value rightOp = stmt.getRightOp();
					callee.add( rightOp.toString());
//					System.out.println("\n callee= "+ callee );
				}
			}
			}
			if(v instanceof InstanceFieldRef) {
				Unit def;
				InstanceFieldRef fr = (InstanceFieldRef) v;
				SootField field = fr.getField();
				soot.Value calee = fr.getBase();
				callee.add(getRealUseObject(calee, u, skipTrivial));
			}else if(v instanceof soot.jimple.ParameterRef) 
				callee.add(v.toString());
			return null;		
		   }


	}

	}
